﻿
using S7Cat.Enums;
using S7Cat.Models;
using S7Cat.Tools;
using System.Runtime.CompilerServices;

namespace S7Cat.Protocol
{
    internal static class PackageBuilder
    {
        public static void WriteReadHeader(ArrayPoolBufferWriter<byte> writer, int length)
        {
            WriteTpktHeader(writer, 19 + 12 * length);
            WriteDataHeader(writer);
            WriteS7Header(writer, 0x01, 2 + 12 * length, 0);
            writer.Write(0x04);
            writer.Write((byte)length);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static void WriteTpktHeader(ArrayPoolBufferWriter<byte> writer, int length)
        {
            writer.Write([0x03, 0x00]);
            writer.WriteBigEndian((ushort)length);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static void WriteDataHeader(ArrayPoolBufferWriter<byte> writer)
        {
            writer.Write([0x02, 0xf0, 0x80]);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static void WriteS7Header(ArrayPoolBufferWriter<byte> writer, byte messageType, int parameterLength, int dataLength)
        {
            writer.Write(0x32); // S7 protocol ID
            writer.Write(messageType); // Message type
            writer.Write([0x00, 0x00, 0x00, 0x00]); // Reserved + PDU ref            
            writer.WriteBigEndian((ushort)parameterLength);
            writer.WriteBigEndian((ushort)dataLength);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void AddReadDataItemAddress(ArrayPoolBufferWriter<byte> writer, DataItemAddress address)
        {
            //single data req = 12
            writer.Write([0x12, 0x0a, 0x10]);
            switch (address.DataType)
            {
                case DataType.Timer:
                case DataType.Counter:
                    writer.Write((byte)address.DataType);
                    break;
                default:
                    writer.Write(0x02);
                    break;
            }

            writer.WriteBigEndian(address.ByteCount);
            writer.WriteBigEndian(address.DB);
            writer.Write((byte)address.DataType);
            var overflow = (int)(address.StartAdr * 8 / 0xffffU); // handles words with address bigger than 8191
            writer.Write((byte)overflow);
            switch (address.DataType)
            {
                case DataType.Timer:
                case DataType.Counter:
                    writer.WriteBigEndian(address.StartAdr);
                    break;
                default:
                    writer.WriteBigEndian((ushort)(address.StartAdr * 8));
                    break;
            }
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void WriteUserDataRequest(ArrayPoolBufferWriter<byte> writer, byte functionGroup, byte subFunction, int dataLength)
        {
            WriteUserDataHeader(writer, 8, dataLength);

            // Parameter
            const byte userDataMethodRequest = 0x11;
            const byte userDataTypeRequest = 0x4;

            // Parameter head
            writer.Write([0x00, 0x01, 0x12]);
            // Parameter length
            writer.Write(0x04);
            // Method
            writer.Write(userDataMethodRequest);
            // Type / function group
            writer.Write((byte)(userDataTypeRequest << 4 | (functionGroup & 0x0f)));
            // Subfunction
            writer.Write(subFunction);
            // Sequence number
            writer.Write(0);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static void WriteUserDataHeader(ArrayPoolBufferWriter<byte> writer, int parameterLength, int dataLength)
        {
            const byte s7MessageTypeUserData = 0x07;

            WriteTpktHeader(writer, 17 + parameterLength + dataLength);
            WriteDataHeader(writer);
            WriteS7Header(writer, s7MessageTypeUserData, parameterLength, dataLength);
        }
    }
}
